﻿package src.game{
	import flash.display.MovieClip;
	import flash.display.Sprite;
	import flash.geom.Point;
	import flash.events.Event;
	import src.tools.HitTester;
	import src.game.elements.GameElement;

	public class Level extends MovieClip{
		private var paddles:Vector.<Paddle>;
		private var objects:Vector.<GameElement>;
		private var b:Ball;
		private var blocksHolder:Sprite;
		private var stageWidth:int = 640;
		private var stageHeight:int = 480;
		
		//next levels cordinates
		private var top, right, bottom, left:int = 0;
		private var nextLevelId:int = 0;
		
		//collisions with paddle modyfiers
		var bounceModyfiers:Vector.<Number> = new <Number>[-0.8, -0.4, -0.1, 0.1, 0.4, 0.8];
		var ballBounceRandomRange:Number = 0.1;
		
		public function Level(levelMap:Array) {
			init();
			initBlocks(levelMap);
			this.addEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
		}
		
		private function init():void {
			blocksHolder = new Sprite();
			paddles = new Vector.<Paddle>;
			objects = new Vector.<GameElement>;
			b = new Ball();
		}
		
		public function setPaddle(pos:Point, size:int, horizontal:Boolean):void {
			var p:Paddle;
			(horizontal) ? p = new Paddle(Paddle.ORIENTATNION_HORIZONTAL) :  p = new Paddle(Paddle.ORIENTATNION_VERTICAL);
			p.setSize(size);
			p.x = pos.x;
			p.y = pos.y;
			this.addChild(p);
			paddles.push(p);
			p = null;
		}
		
		public function setNextLevelIds(top:int, right:int, down:int, left:int):void {
			this.top = top;
			this.right = right;
			this.bottom = down;
			this.left = left;
		}
		
		private function setPaddlesMovementRanges():void {
			for (var i:int = 0; i < paddles.length; i++ ) {
				var p:Paddle = (Paddle)(paddles[i]);
				var x:int = p.getX() - p.getWidth()/2;
				var y:int = p.getY() - p.getHeight()/2;
				//setting min range
				if (p.getOrientation() == Paddle.ORIENTATNION_HORIZONTAL ) {
					while ( !HitTester.hitTestPoint(blocksHolder, new Point(x, y) ) && x>=0 )
						x--;
						p.setMinRange(x + p.getWidth() / 2);
					}
				else {
					while ( !HitTester.hitTestPoint(blocksHolder, new Point(x, y) ) && y>=0 )
						y--;
						p.setMinRange(y + p.getHeight() / 2);
				}
				//setting max range
				x = p.getX() + p.getWidth()/2;
				y = p.getY() + p.getHeight()/2;
				if (p.getOrientation() == Paddle.ORIENTATNION_HORIZONTAL ) {
					while ( !HitTester.hitTestPoint(blocksHolder, new Point(x, y) ) && x<stageWidth )
					x++;
					p.setMaxRange(x - p.getWidth() / 2);
				}
				else {
					while ( !HitTester.hitTestPoint(blocksHolder, new Point(x, y) ) && y<stageHeight )
					y++;
					p.setMaxRange(y - p.getHeight() / 2);
				}
			}
		}
		
		private function initBlocks(map:Array):void {
			for (var i:int = 0; i<map.length; i++)
				for (var j:int = 0; j<map[i].length; j++)
				if (map[i][j] > 0) {
					var b:GameElement = new GameElement();
					b.gotoAndStop(map[i][j]);
					b.x = j * 32;
					b.y = i * 32;
					b.width = 32;
					b.height = 32;
					//jesli element jest statyczny dodajemy go do tła
					if (map[i][j] > 1) {
						objects.push(b);
						this.addChild(b);
						b.addEventListener(GameEvent.REMOVE_OBJECT, removeObject);
					}
					else
						blocksHolder.addChild(b);
				}
		}
		
		public function initBall(position:Point, movement:Point, speed:int = 0):void {
			b.x = position.x;
			b.y = position.y;
			b.setHorizontalMovement(movement.x);
			b.setVerticalMovement(movement.y);
		}
		
		public function movePaddle(x:int, y:int) {
			for (var i:int = 0; i<paddles.length; i++) {
				var p:Paddle = (Paddle)(paddles[i]);
				var newX:int = p.getX();
				var newY:int = p.getY();
				if (p.getOrientation() == Paddle.ORIENTATNION_HORIZONTAL)
					newX = Math.min(Math.max(x, p.getMinRange() ), p.getMaxRange() );
				else if (p.getOrientation() == Paddle.ORIENTATNION_VERTICAL)
					newY = Math.min(Math.max(y, p.getMinRange() ), p.getMaxRange() );
				p.moveTo(newX, newY);
			}
		}

		public function moveBall():void {
			b.move();
		}
		
		public function checkCollisions():void {
			/*if (checkBorderCollisions() )
				createBounce(b.x, b.y); */
			if (checkBlocksCollisions() ) 
				createBounce(b.x, b.y);
			if (checkObjectsCollisions() )
				createBounce(b.x, b.y);
			if (checkPaddlesCollisions() )
				createBounce(b.x, b.y);
		}
		
		public function checkForNextLevel():void {
			var next:Boolean = false;
			if (b.x < 0) {
				next = true;
				nextLevelId = left; 
			}
			else if (b.x > stageWidth) {
				next = true;
				nextLevelId = right; 
			}
			else if (b.y < 0) {
				next = true;
				nextLevelId = top; 
			}
			else if (b.y > stageHeight) {
				next = true;
				nextLevelId = bottom; 
			}
			
			if (next) {
				dispatchEvent(new GameEvent(GameEvent.LEVEL_CLEARED, true, false));
			}
		}
		
		public function getBallNewPosition():Point {
			var x, y:Number;
			if (b.x < 0) {x = stageWidth - b.width/2; y = b.y; }
			else if (b.x > stageWidth) {x = 0 + b.width/2; y = b.y; }
			if (b.y < 0) { x = b.x; y = stageHeight - b.height/2; }
			else if (b.y > stageHeight) { x = b.x;  y = 0 +  b.height/2; }
			return new Point(x, y);
		}
		
		public function getBallDirection():Point {
			return new Point(b.getHorizonalMovement(), b.getVerticalMovement() );
		}
		
		public function getBallSpeed():Number {
			return b.getVelocity();
		}
		
		private function checkBorderCollisions():Boolean {
			var col:Boolean = false;
			if ( b.x - b.width/2 < 0 )  {
				b.setMoveRight();
				col = true;
			}
			if ( b.x + b.width/2 > stageWidth ) {
				b.setMoveLeft();
				col = true;
			}
			if ( b.y - b.height/2 < 0 ) {
				b.setMoveDown();
				col = true;
			}
			if ( b.y + b.height > stageHeight ) {
				b.setMoveUp();
				col = true;
			}
			return col;
		}
		
		private function checkObjectsCollisions():Boolean {
			var col:Boolean = false;
			for (var i:int = objects.length - 1; i>=0; i--) {
				if (HitTester.hitTestObject(b, GameElement (objects[i]) ) ) {
					col = true;
					var el:GameElement = GameElement((MovieClip(objects[i])).getChildAt(0) );
					el.onHit();
					collisionWithObject(el);
				}
			}
			return col;
		}
		
		private function checkBlocksCollisions():Boolean {
			var col:Boolean = false;
			if (HitTester.hitTestObject(b, blocksHolder) ) {
				col = true;
				collisionWithObject(blocksHolder);
			}
			return col;
		}
		
		private function collisionWithObject(s:Sprite) {
			var col:Boolean = false;
				//left and right points
				if ( HitTester.hitTestPoint(s, new Point(b.x, b.y - b.height/2) )  || HitTester.hitTestPoint(s, new Point(b.x, b.y + b.height/2) ) ) {
					col = true;
					b.toggleVertical();
				}
				//top and bottom points
				if ( HitTester.hitTestPoint(s, new Point(b.x + b.width/2, b.y) ) || HitTester.hitTestPoint(s, new Point(b.x - b.width/2, b.y)) ) {
					col = true;
					b.toggleHorizontal();
				}
				if (!col) {
					col = true;
					b.toggleVertical();
					b.toggleHorizontal();
				}
				if (col)
					while (HitTester.hitTestObject(b, blocksHolder))
						b.bounce();
				createRandomMovement();
				
		}
		
		private function createRandomMovement():void {
			b.setHorizontalMovement(b.getHorizonalMovement() + (Math.random() * ballBounceRandomRange) - ballBounceRandomRange/2);
			b.setVerticalMovement(b.getVerticalMovement() + (Math.random() * ballBounceRandomRange) - ballBounceRandomRange/2)
		}

		private function collidesWithPaddle():Boolean {
			for (var i:int = 0; i<paddles.length; i++)
				if (HitTester.hitTestObject(b, Paddle(paddles[i])) ) 
					return true;
			return false;
		}
		
		private function checkPaddlesCollisions():Boolean {
			var col:Boolean = false;
			for (var i:int = 0; i<paddles.length; i++) {
				var p:Paddle = Paddle(paddles[i]);
				if (HitTester.hitTestObject(b, p) ) {
					//normalny przypadek kolizji
					var piece:Number = 0;
					var pos:int =  bounceModyfiers.length - 1;
					if (p.getOrientation() == Paddle.ORIENTATNION_HORIZONTAL ) {
						b.toggleVertical();
						piece = p.getWidth() / bounceModyfiers.length;
						while (b.x < p.getX() - p.getWidth()/2 + pos  * piece && pos>0) 
							pos--;
						trace(pos);
						b.setHorizontalMovement(bounceModyfiers[pos]);
					}
					else if (p.getOrientation() == Paddle.ORIENTATNION_VERTICAL ){
						b.toggleHorizontal();
						piece = p.getHeight() / bounceModyfiers.length;
						while (b.y < p.getY() - p.getHeight()/2 + pos  * piece && pos>0) 
							pos--;
						trace(pos);
						b.setVerticalMovement(bounceModyfiers[pos]);
					}
					//sytuacja w której gracz niespodziewanie najeżdza na piłkę i znajduje się ona wewnątrz paletki
					//pętla while ma na celu "wypchnięcie piłki" poza obszar paletki, ale tak aby nie spowodować kolizji
					//z otoczeniem
					while (HitTester.hitTestObject(b, Paddle(paddles[i])) &&  !HitTester.hitTestObject(b, blocksHolder)) {
						b.bounce();
					}
					//sprawdzenie jeszcze raz kolizji ze ścianami, na wypadek, gdyby "wypychana" piłka z paletki kolidowałą
					//ze ścianą. wtedy należy wypchnąć piłkę tak, aby nie kolidowała z żadnym z tych obiektów.
					if ( checkBlocksCollisions() ) {
						while (HitTester.hitTestObject(b, blocksHolder) || HitTester.hitTestObject(b, Paddle(paddles[i])) )
							b.bounce();
					}
					col = true;
				}
			}
			return col;
		}
		
		public function createBounce(x:int, y:int):void {
			var b:Bounce = new Bounce();
			b.x = x;
			b.y = y;
			this.addChild(b);
		}
		
		private function onAddedToStage(event:Event):void {
			this.removeEventListener(Event.ADDED_TO_STAGE, onAddedToStage);
			this.addChild(blocksHolder);
			for (var i:int = 0; i<paddles.length; i++)
				this.addChild(Paddle(paddles[i]));
			setPaddlesMovementRanges();
			this.addChild(b);
		}
		
		private function removeObject(event:Event) {;
			var g:GameElement = GameElement(event.target);
			g.removeEventListener(GameEvent.REMOVE_OBJECT, removeObject);
			objects.splice(objects.indexOf(g.parent), 1);
			trace(objects);
		}
		
		public function getNextLevelId():int {
			return nextLevelId;
		}
	}
}